// ImgDialogBase.cpp : implementation file
//

#include "stdafx.h"
#include "ImgDialogBase.h"
#include "Utility.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#ifndef WS_EX_LAYERED
#	define WS_EX_LAYERED			0x00080000
#endif

#ifndef WS_EX_NOACTIVATE
#	define WS_EX_NOACTIVATE			0x08000000L
#endif

#ifndef LWA_ALPHA
#	define LWA_ALPHA				0x00000002
#endif

#ifndef ULW_ALPHA
#	define ULW_ALPHA				0x00000002
#endif

#ifndef AC_SRC_ALPHA
#	define AC_SRC_ALPHA				0x01
#endif

std::map<HWND, tagCtrlInfo>	CImgDialogBase::s_mpCtrl;
PFN_SetLayeredWindowAttributes CImgDialogBase::s_pfnSetLayeredWindowAttributes = NULL;
PFN_UpdateLayeredWindow CImgDialogBase::s_pfnUpdateLayeredWindow = NULL;
PFN_GetGUIThreadInfo CImgDialogBase::s_pfnGetGUIThreadInfo = NULL;

/////////////////////////////////////////////////////////////////////////////
// CImgDialogBase dialog

CImgDialogBase::CImgDialogBase( UINT nIDD
			   , LPCWSTR lpszFile
			   , CWnd* pParent
			   )
			   : CDialog( nIDD, pParent)
			   , m_pImage( new Image(lpszFile) )
			   , m_hFakeWnd(NULL)
			   , m_bIsRefreshing(FALSE)
			   , m_nAlpha(0)
			   , m_bShown(FALSE)
{
	//{{AFX_DATA_INIT(CImgDialogBase)
	// NOTE: the ClassWizard will add member initialization here
	//}}AFX_DATA_INIT
	m_strWndClassName.Format( _T("FakeWnd%X%X%X%d")
		, GetTickCount()
		, GetCurrentProcessId()
		, GetCurrentThreadId()
		, nIDD
		);
}

CImgDialogBase::CImgDialogBase( UINT nIDD
			   , UINT nImgID
			   , LPCTSTR lpszType
			   , HINSTANCE hResourceModule
			   , CWnd* pParent
		)
		: CDialog( nIDD, pParent)
		, m_pImage( CUtility::LoadImage( nImgID, lpszType, hResourceModule) )
		, m_hFakeWnd(NULL)
		, m_bIsRefreshing(FALSE)
		, m_bShown(FALSE)
		, m_nAlpha(0)
{
	//{{AFX_DATA_INIT(CImgDialogBase)
	// NOTE: the ClassWizard will add member initialization here
	//}}AFX_DATA_INIT
	m_strWndClassName.Format( _T("FakeWnd%X%X%X%d")
		, GetTickCount()
		, GetCurrentProcessId()
		, GetCurrentThreadId()
		, nIDD
		);
}

CImgDialogBase::~CImgDialogBase()
{
	if( m_pImage )
	{
		delete m_pImage;
		m_pImage = NULL;
	}
}

void CImgDialogBase::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CImgDialogBase)
		// NOTE: the ClassWizard will add DDX and DDV calls here
	//}}AFX_DATA_MAP
}


BEGIN_MESSAGE_MAP(CImgDialogBase, CDialog)
	//{{AFX_MSG_MAP(CImgDialogBase)
	ON_WM_LBUTTONDOWN()
	ON_WM_ERASEBKGND()
	ON_WM_MOVE()
	ON_WM_SIZE()
	ON_WM_SHOWWINDOW()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CImgDialogBase message handlers

BOOL CImgDialogBase::OnInitDialog() 
{
	CDialog::OnInitDialog();

	HMODULE hUser32Dll = ::GetModuleHandle(_T("user32.dll"));
	ASSERT(hUser32Dll);

	if( !s_pfnSetLayeredWindowAttributes )
	{
		s_pfnSetLayeredWindowAttributes
			= (PFN_SetLayeredWindowAttributes)::GetProcAddress( hUser32Dll, "SetLayeredWindowAttributes");
		ASSERT(s_pfnSetLayeredWindowAttributes);
	}

	if( !s_pfnUpdateLayeredWindow )
	{
		s_pfnUpdateLayeredWindow
			= (PFN_UpdateLayeredWindow)::GetProcAddress( hUser32Dll, "UpdateLayeredWindow");
		ASSERT(s_pfnUpdateLayeredWindow);
	}

	if( !s_pfnGetGUIThreadInfo )
	{
		s_pfnGetGUIThreadInfo
			= (PFN_GetGUIThreadInfo)::GetProcAddress( hUser32Dll, "GetGUIThreadInfo");
		ASSERT(s_pfnGetGUIThreadInfo);
	}

	if( m_pImage )
	{
		// Set the dialog style
		::SetWindowPos( GetSafeHwnd()
			, NULL
			, 0
			, 0
			, m_pImage->GetWidth()
			, m_pImage->GetHeight()
			, SWP_NOMOVE | SWP_NOZORDER
			);
		
		::SetWindowLong( m_hWnd, GWL_EXSTYLE, GetWindowLong(m_hWnd, GWL_EXSTYLE) | WS_EX_LAYERED);
		BYTE bTran = 0x05;
		s_pfnSetLayeredWindowAttributes( m_hWnd, 0, bTran, LWA_ALPHA);
		CreateFakeWnd();

		HookControlUpdate(GetSafeHwnd());
	}
	
	
	
	return TRUE;  // return TRUE unless you set the focus to a control
	              // EXCEPTION: OCX Property Pages should return FALSE
}

void CImgDialogBase::OnLButtonDown(UINT nFlags, CPoint point) 
{
	::SendMessage( GetSafeHwnd(), WM_SYSCOMMAND, 0xF012, 0);  
	
	CDialog::OnLButtonDown(nFlags, point);
}

BOOL CImgDialogBase::OnEraseBkgnd(CDC* pDC) 
{
	return TRUE;
}

void CImgDialogBase::OnMove(int x, int y) 
{
	CDialog::OnMove(x, y);
	
	if( ::IsWindow(m_hFakeWnd) )
	{
		::SetWindowPos( m_hFakeWnd
			, HWND_TOP
			, x
			, y
			, 0
			, 0
			, SWP_NOSIZE
			);
	}	
}

void CImgDialogBase::OnSize(UINT nType, int cx, int cy) 
{
	CDialog::OnSize(nType, cx, cy);
	
	if( ::IsWindow(m_hFakeWnd) )
	{
		::SetWindowPos( m_hFakeWnd
			, HWND_TOP
			, 0
			, 0
			, cx
			, cy
			, SWP_NOMOVE
			);	
	}		
}


void CImgDialogBase::OnShowWindow(BOOL bShow, UINT nStatus) 
{
	CDialog::OnShowWindow(bShow, nStatus);
	
	::ShowWindow( m_hFakeWnd, bShow ? SW_SHOWNA : SW_HIDE);

	if( !m_bShown )
	{
		m_bShown = TRUE;
		AfxBeginThread( (AFX_THREADPROC)ShowMotionThread, this);
	}
}


//-------------------------------------------------------------------------
// Function Name    :DestoryFakeWnd
// Parameter(s)     :
// Return           :
// Create			:2007-5-8 11:43 Jerry.Wang
// Memo             :Destory the fake wnd
//-------------------------------------------------------------------------
void CImgDialogBase::DestoryFakeWnd(void)
{
	if( m_hFakeWnd != NULL )
	{
		::DestroyWindow(m_hFakeWnd);
		m_hFakeWnd = NULL;
		
		::UnregisterClass( m_strWndClassName, ::GetModuleHandle(NULL) );
	}
}

//-------------------------------------------------------------------------
// Function Name    :CreateFakeWnd
// Parameter(s)     :
// Return           :
// Create			:2007-5-7 18:50 Jerry.Wang
// Memo             :create the fake window
//-------------------------------------------------------------------------
void CImgDialogBase::CreateFakeWnd(void)
{
	DestoryFakeWnd();
	
	WNDCLASSEX wcex;
	
	memset(&wcex, 0, sizeof(wcex));
	
	wcex.cbSize = sizeof(WNDCLASSEX); 
	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= ::DefWindowProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= ::GetModuleHandle(NULL);
	wcex.hIcon			= NULL;
	wcex.hCursor		= ::LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= NULL;
	wcex.lpszMenuName	= NULL;
	wcex.lpszClassName	= m_strWndClassName;
	wcex.hIconSm		= NULL;
	
	VERIFY( RegisterClassEx(&wcex) );
	
	CRect rc;
	GetWindowRect(rc);
	m_hFakeWnd = ::CreateWindowEx( WS_EX_WINDOWEDGE | WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_NOACTIVATE
		, m_strWndClassName
		, NULL
		, WS_VISIBLE | WS_POPUP 
		, rc.left
		, rc.top
		, rc.Width()
		, rc.Height()
		, GetSafeHwnd()
		, NULL
		, ::GetModuleHandle(NULL)
		, NULL
		);
	ASSERT( m_hFakeWnd != NULL );
}


void CImgDialogBase::HookControlUpdate(HWND hWnd)
{
	tagCtrlInfo stCtrlInfo = {0};
	stCtrlInfo.pImgDlg = this;
	stCtrlInfo.pWndProc = (WNDPROC)::GetWindowLong( hWnd, GWL_WNDPROC);
	s_mpCtrl[hWnd] = stCtrlInfo;
	::SetWindowLong( hWnd, GWL_WNDPROC, (LONG)(&CImgDialogBase::CtrlWndProc) );
	
	HWND hChild = ::GetWindow( hWnd, GW_CHILD);  
	while(hChild)   
	{
		HookControlUpdate(hChild);
		hChild = ::GetWindow( hChild, GW_HWNDNEXT);   
	}
}

void CImgDialogBase::UnhookControlUpdate(HWND hWnd)
{
	if( !::IsWindow(hWnd) )
		return;
	
	std::map<HWND, tagCtrlInfo>::iterator iter;
	iter = s_mpCtrl.find(hWnd);
	if( iter != s_mpCtrl.end() )
		s_mpCtrl.erase(iter);
	
	HWND hChild = ::GetWindow( hWnd, GW_CHILD);  
	while(hChild)   
	{
		UnhookControlUpdate(hChild);
		hChild = ::GetWindow( hChild, GW_HWNDNEXT);   
	}
}


LRESULT CALLBACK CImgDialogBase::CtrlWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
	std::map<HWND, tagCtrlInfo>::iterator iter;
	iter = s_mpCtrl.find(hWnd);
	if( iter == s_mpCtrl.end() )
		return ::DefWindowProc( hWnd, nMsg, wParam, lParam);
	
	LRESULT lResult = ::CallWindowProc( iter->second.pWndProc, hWnd, nMsg, wParam, lParam);
	switch (nMsg)
	{
		case WM_PAINT:
		case WM_CAPTURECHANGED:
		//case WM_NCPAINT:
		//case WM_CTLCOLOR:
		case WM_CTLCOLOREDIT:
		case WM_CTLCOLORBTN:
		case WM_CTLCOLORSTATIC:
		case WM_CTLCOLORMSGBOX:
		case WM_CTLCOLORDLG:
		case WM_CTLCOLORLISTBOX:
		case WM_CTLCOLORSCROLLBAR:
			{
				iter->second.pImgDlg->Refresh();
				break;
			}
			
		default:
			break;
	}
	return lResult;
}



void CImgDialogBase::Refresh(void)
{
	if( !IsWindow(m_hFakeWnd) )
		return;
	
	if( m_bIsRefreshing )
		return;
	
	m_bIsRefreshing = TRUE;
	
	RECT rc;
	::GetWindowRect( m_hFakeWnd, &rc);
	POINT ptSrc = { 0, 0};
	POINT ptWinPos = { rc.left, rc.top};
	SIZE szWin = { m_pImage->GetWidth(), m_pImage->GetHeight() };
	BLENDFUNCTION stBlend = {AC_SRC_OVER, 0, m_nAlpha, AC_SRC_ALPHA};
	
	HDC hDC = ::GetDC(NULL);
	HDC hdcMemory = ::CreateCompatibleDC(hDC);

	BITMAPINFOHEADER stBmpInfoHeader = { 0 };   
	int nBytesPerLine = ((szWin.cx * 32 + 31) & (~31)) >> 3;
	stBmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER);   
	stBmpInfoHeader.biWidth = szWin.cx;   
	stBmpInfoHeader.biHeight = szWin.cy;   
	stBmpInfoHeader.biPlanes = 1;
	stBmpInfoHeader.biBitCount = 32;   
	stBmpInfoHeader.biCompression = BI_RGB;   
	stBmpInfoHeader.biClrUsed = 0;   
	stBmpInfoHeader.biSizeImage = nBytesPerLine * szWin.cy;	
	
	PUINT32 pvBits = NULL;   
	HBITMAP hbmpMem = ::CreateDIBSection(NULL, (PBITMAPINFO)&stBmpInfoHeader, DIB_RGB_COLORS, (LPVOID*)&pvBits, NULL, 0);
	ASSERT(hbmpMem != NULL);
	if(hbmpMem)   
	{   
		HGDIOBJ hbmpOld = ::SelectObject( hdcMemory, hbmpMem); 
		
		{
			Graphics graph(hdcMemory);

			graph.SetSmoothingMode(SmoothingModeNone);

			graph.DrawImage( m_pImage, 0, 0, szWin.cx, szWin.cy);

			// Draw all the controls
			HWND hwndChild = ::GetWindow( GetSafeHwnd(), GW_CHILD);  
			while(hwndChild)   
			{
				DrawCtrl( graph, hDC, hwndChild);
				hwndChild = ::GetWindow( hwndChild, GW_HWNDNEXT);   
			}

			DrawCaret(graph);
		}
	
		
		s_pfnUpdateLayeredWindow( m_hFakeWnd
			, hDC
			, &ptWinPos
			, &szWin
			, hdcMemory
			, &ptSrc
			, 0
			, &stBlend
			, ULW_ALPHA
			);

		::SelectObject( hdcMemory, hbmpOld);   
		::DeleteObject(hbmpMem); 
	}
	
	
	::DeleteDC(hdcMemory);
	::DeleteDC(hDC);
	
	m_bIsRefreshing = FALSE;
}



void CImgDialogBase::DrawCtrl( Graphics & graphics, HDC hDC, HWND hWnd)
{
	if( !::IsWindow(hWnd) )
		return;
	
	RECT rc;
	::GetWindowRect( hWnd, &rc);
	ScreenToClient(&rc);
	
	HDC hdcMemory = ::CreateCompatibleDC(hDC);
	HBITMAP hBitmap = ::CreateCompatibleBitmap( hDC, rc.right - rc.left, rc.bottom - rc.top);
	HGDIOBJ hbmpOld = ::SelectObject( hdcMemory, hBitmap); 
	
	::SendMessage( hWnd, WM_PRINT, (WPARAM)hdcMemory, (LPARAM)PRF_NONCLIENT | PRF_CLIENT | PRF_CHILDREN | PRF_CHECKVISIBLE | PRF_ERASEBKGND | PRF_OWNED);
	
	Bitmap bitmap( hBitmap, NULL);
	graphics.DrawImage( &bitmap, rc.left, rc.top);
	
	::SelectObject( hdcMemory, hbmpOld); 
	::DeleteDC(hdcMemory);
	::DeleteObject(hBitmap); 
}


void CImgDialogBase::DrawCaret(Graphics & graph)
{
	GUITHREADINFO stThreadInfo = {0};
	stThreadInfo.cbSize = sizeof(GUITHREADINFO);
	BOOL bRet = s_pfnGetGUIThreadInfo( ::GetCurrentThreadId(), &stThreadInfo);

	if( !bRet || !::IsWindow(stThreadInfo.hwndCaret) )
		return;

	POINT ptCaret = { stThreadInfo.rcCaret.left, stThreadInfo.rcCaret.top};
	int nHeight = stThreadInfo.rcCaret.bottom - stThreadInfo.rcCaret.top;
	::ClientToScreen( stThreadInfo.hwndCaret, &ptCaret);
	::ScreenToClient( m_hFakeWnd, &ptCaret);
	Pen pen( Color::Black, 1.0f);
	graph.DrawLine( &pen
		, ptCaret.x
		, ptCaret.y
		, ptCaret.x
		, ptCaret.y + nHeight
		);
}



UINT __cdecl CImgDialogBase::ShowMotionThread( LPVOID pParam )
{
	CImgDialogBase * pThis = static_cast<CImgDialogBase*>(pParam);
	
	while (pThis->m_nAlpha < 255)
	{
		pThis->m_nAlpha += 30;
		if( pThis->m_nAlpha > 255 )
			pThis->m_nAlpha = 255;
		
		::SendMessage( pThis->GetSafeHwnd(), WM_PAINT, 0, 0);
		Sleep(60);
	}
	
	return 0;
}


void CImgDialogBase::PostNcDestroy()
{
	UnhookControlUpdate(GetSafeHwnd());
	DestoryFakeWnd();
	
	CDialog::PostNcDestroy();
}
